package com.ktwlsoft.fundmanage.service;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.validation.constraints.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

/**
 * 会员中心原生http封装
 *
 * @author zhouxuanhong
 * @date 2019-09-12 14:57
 * @since JDK1.8
 */
@Component
@Slf4j
public class HttpUtils {
    private final static String GET = "GET";

    private final static String POST = "POST";

    private static HttpUtils utils;

    @Value("${inner-communicate.host}")
    private String ipInfo;

    @Value("${inner-communicate.signature-key}")
    private String signatureKey;

    @Value("${inner-communicate.client-id}")
    private String clientId;

    @Value("${inner-communicate.client-secret}")
    private String clientSecret;


    /**
     * 发送请求
     *
     * @param uri        ${@link String} 请求uri
     * @param headParam  ${@link Map} 自定义请求头信息
     * @param method     ${@link String} 请求方法
     * @param params     ${@link String} 请求参数
     * @param isSetToken ${@link Boolean} 是否设置token
     * @return JSONObject ${@link JSONObject}
     * @author zhouxuanhong
     * @date 2019-10-30 10:37
     */
    private static JSONObject send(@NotNull String uri, Map<String, String> headParam, String method, String params, boolean isSetToken) {
        String url = utils.ipInfo + uri;
        JSONObject result = new JSONObject();
        result.put("code", 100);
        result.put("success", false);
        StringBuilder res = new StringBuilder();
        OutputStreamWriter out = null;
        BufferedReader reader = null;
        try {
            if (GET.equals(method)) {
                url += "?" + params;
            }
            log.debug("----------HttpUtils----发起请求------>请求地址:{},请求方法:{},请求参数:{}", url, method, params);
            URL connUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) connUrl.openConnection();
            if (POST.equals(method)) {
                connection.setDoOutput(true);
                connection.setDoInput(true);
                connection.setUseCaches(false);
                connection.setInstanceFollowRedirects(true);
            }
            connection.setRequestMethod(method);
            connection.setRequestProperty("Content-Type", "application/json");
            if (!CollectionUtils.isEmpty(headParam)) {
                headParam.forEach(connection::setRequestProperty);
            }
            // 设置token
            if (isSetToken) {
                utils.setToken(connection);
            }
            connection.connect();
            if (POST.equals(method)) {
                out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
                out.append(params);
                out.flush();
            }
            // 读取响应
            reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            while ((line = reader.readLine()) != null) {
                res.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
            result.put("message", "请求异常:" + e.getMessage());
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                result.put("message", "请求异常:" + e.getMessage());
            }
        }
        String middleResult = res.toString();
        if (StringUtils.isNotBlank(middleResult)) {
            result = JSONObject.parseObject(middleResult);
        } else if (result != null && !result.isEmpty()) {
            result.put("message", "请求结果异常");
        }
        log.debug("----------HttpUtils-----获取结果----->请求地址:{},请求方法:{},请求参数:{},请求结果:{}", url, method, params, result);
        return result;
    }


    /**
     * 发送get请求
     *
     * @param uri        ${@link String} 请求uri
     * @param params     ${@link Object} 请求参数
     * @param isSetToken ${@link Boolean} 是否设置请求token，默认不需要
     * @param isSetSign  ${@link Boolean} 是否设置签名，默认不需要
     * @return JSONObject ${@link JSONObject}
     * @author zhouxuanhong
     * @date 2019-10-30 10:35
     */
    public static JSONObject sendGetJson(@NotNull String uri, Map<String, String> headParam, boolean isSetToken, boolean isSetSign, Map<?, ?> params) {
        String getString = "";
        if (!CollectionUtils.isEmpty(params)) {
            if (isSetSign) {
                String jsonString = utils.setSign(JSONObject.toJSONString(params));
                getString = toGetParams(JSONObject.parseObject(jsonString));
            } else {
                getString = toGetParams(params);
            }
        }
        return send(uri, null, GET, getString, isSetToken);
    }


    /**
     * 发送post请求
     *
     * @param uri        ${@link String} 请求uri
     * @param params     ${@link Object} 请求参数
     * @param isSetToken ${@link Boolean} 是否设置请求token，默认不需要
     * @param isSetSign  ${@link Boolean} 是否设置签名，默认不需要
     * @return JSONObject ${@link JSONObject}
     * @author zhouxuanhong
     * @date 2019-10-30 10:35
     */
    public static JSONObject sendPostJson(@NotNull String uri, Object params, Boolean isSetToken, Boolean isSetSign) {
        String jsonString = JSONObject.toJSONString(params);
        if (isSetToken == null) {
            isSetToken = false;
        }
        if (isSetSign != null && isSetSign) {
            jsonString = utils.setSign(jsonString);
        }
        return send(uri, null, POST, jsonString, isSetToken);
    }


    /**
     * 生成签名字段
     *
     * @param map       ${@link SortedMap}
     * @param key       ${@link String}
     * @param timestamp ${@link String}
     * @return String ${@link String}
     * @author zhouxuanhong
     * @date 2019-06-14 14:30
     */
    private static String encodeSign(SortedMap<String, String> map, String key, String timestamp) {
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Iterator<Map.Entry<String, String>> iterator = entries.iterator();
        List<String> values = Lists.newArrayList();
        while (iterator.hasNext()) {
            Map.Entry entry = iterator.next();
            String v = String.valueOf(entry.getValue());
            String k = String.valueOf(entry.getKey());
            if (StringUtils.isNotEmpty(v) && entry.getValue() != null && !"sign".equals(k) && !"key".equals(k)) {
                values.add(k + "=" + v);
            }
        }
        values.add("timestamp=" + timestamp);
        values.add("key=" + key);
        String sign = StringUtils.join(values, "&");
        return encodeByMD5(sign).toUpperCase();
    }


    /**
     * MD5加密
     *
     * @param str ${@link String}
     * @return String ${@link String}
     * @author zhouxuanhong
     * @date 2019-06-14 14:30
     */
    private static String encodeByMD5(String str) {
        if (str == null) {
            return null;
        }
        return DigestUtils.md5Hex(str);
    }


    /**
     * Map转为get地址参数
     *
     * @param source ${@link Map}
     * @return String ${@link String}
     * @author zhouxuanhong
     * @date 2019-10-30 10:33
     */
    private static String toGetParams(Map<?, ?> source) {
        return toGetParams(JSONObject.parseObject(JSONObject.toJSONString(source)));
    }


    /**
     * JSONObject转为get地址参数
     *
     * @param jsonObject ${@link JSONObject}
     * @return String ${@link String}
     * @author zhouxuanhong
     * @date 2019-10-30 10:30
     */
    private static String toGetParams(JSONObject jsonObject) {
        StringBuilder sb = new StringBuilder();
        boolean flag = false;
        for (String key : jsonObject.keySet()) {
            Object value = jsonObject.get(key);
            if (!flag) {
                flag = true;
                sb.append(key).append("=").append(value);
            } else {
                sb.append("&").append(key).append("=").append(value);
            }
        }
        return sb.toString();
    }


    @PostConstruct
    private void init() {
        utils = this;
    }


    /**
     * 处理设置token
     *
     * @param connection ${@link HttpURLConnection}
     * @author zhouxuanhong
     * @date 2019-10-30 10:29
     */
    private void setToken(HttpURLConnection connection) {
        Map<String, String> tokenParams = new HashMap<>(3);
        tokenParams.put("client_id", clientId);
        tokenParams.put("client_secret", clientSecret);
        tokenParams.put("grant_type", "client_credentials");
        JSONObject jsonObject = sendGetJson("/oauth/access_token", null, false, false, tokenParams);
        if (jsonObject != null && !jsonObject.isEmpty()) {
            String accessToken = jsonObject.getString("access_token");
            connection.setRequestProperty("Authorization", "bearer " + accessToken);
        } else {
            throw new RuntimeException("获取token异常");
        }
    }


    /**
     * 处理生成签名密钥
     *
     * @param jsonString ${@link String}
     * @return String ${@link String}
     * @author zhouxuanhong
     * @date 2019-10-30 10:30
     */
    private String setSign(String jsonString) {
        JSONObject jsonObject = JSONObject.parseObject(jsonString);
        Map<String, String> map = JSONObject.parseObject(jsonString, new TypeReference<Map<String, String>>() {
        });
        SortedMap<String, String> sort = new TreeMap<>(map);
        // 生成签名并回填到请求参数中
        String timestamp = Long.toString(System.currentTimeMillis());
        String sign = encodeSign(sort, signatureKey, timestamp);
        jsonObject.put("sign", sign);
        jsonObject.put("timestamp", timestamp);
        return JSONObject.toJSONString(jsonObject);
    }
}
